const { Document, EmbeddedDocument } = require('camo')

class Move extends Document {
  constructor() {
    super()

    this.schema({
      // For prompts
      name: {type: String, required: true},
      description: {type: String, default: ''},
      emote: {type: String, required: true},

      // Time after choice of move that it is actually used
      // eg. most magic attacks take longer to cast
      prepareTicks: {type: Number, min: 1, required: true},

      // See respective classes
      target: TargetDesc,
      actions: [ActionDesc],
    })
  }

  // Performs (read: acts out) this.actions on a single target.
  async performOn(target, user, battle) {
    for (const action of this.actions) {
      const actionTarget = action.to === 'target' ? target : user

      if (action.type === 'damage') {
        // TODO defense stat, etc
        const { amount } = action.data
        const healthState = await actionTarget.modHealth(-amount, battle.game.guild)

        if (healthState === 'dead') {
          await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** died.`)
        } else {
          await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** took ${amount} damage.`)
        }
      } else if (action.type === 'heal') {
        const { amount } = action.data

        if (actionTarget.healthState === 'dead') {
          await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** is dead and cannot be healed normally.`)
        } else if (actionTarget.healthState === 'healthy') {
          await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** is already at max health.`)
        } else {
          await actionTarget.modHealth(+amount, battle.game.guild)
          await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** recovered ${amount} health.`)
        }
      } else {
        throw 'Unknown action descriptor type: ' + action.type
      }
    }
  }

  // Creates a new move, or re-uses if one with the same name is already found.
  static async upsert(emote, name, description, prepareTicks, {target, actions=[]}={}) {
    const existing = await Move.findOne({name})

    if (existing) {
      return existing
    } else {
      const move = Move.create({
        name, description, emote,
        prepareTicks,
        target: target instanceof TargetDesc ? target : TargetDesc.of(target),
        actions: actions.map(a => a instanceof ActionDesc ? a : ActionDesc.create(a)),
      })

      return move.save()
    }
  }
}

// Target descriptor - describes the number and types of character a move can
// be used on (TODO: under certain conditions).
class TargetDesc extends EmbeddedDocument {
  constructor() {
    super()

    this.schema({
      type: {type: String, choices: ['self', 'party', 'enemy', 'any']},

      numberMin: {type: Number, min: 1, default: 1},
      numberMax: {type: Number, min: 1, default: 1},
      //condition: ConditionDesc,
    })
  }

  static of(type, numberMin=1, numberMax) {
    if (numberMax === undefined) {
      // 'Up to'
      numberMax = numberMin
      numberMin = 1
    }

    return TargetDesc.create({type, numberMin, numberMax})
  }
}

// Action descriptor - describes the effect a move has on the target or the
// user. Includes status-effect inflictions and damage-dealing.
class ActionDesc extends EmbeddedDocument {
  constructor() {
    super()

    this.schema({
      type: {type: String, required: true, choice: ['damage', 'heal']},
      data: Object, // Depending on type
      to: {type: String, required: 'target', choice: ['user', 'target']}
      //condition: ConditionDesc,
    })
  }
}

// TODO: Condition descriptor - eg. carrying Potato AND hp < 10
// We should make a DSL that lets you easily define these using simple syntax.

module.exports = Object.assign(Move, {TargetDesc, ActionDesc})
